package DyingBaby.backend.tm;

import DyingBaby.common.Error;
import DyingBaby.utils.Panic;
import DyingBaby.utils.Parser;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 事务管理器实现类
 * 负责管理事务的生命周期和状态持久化
 * 使用文件存储事务状态，保证事务的原子性和持久性
 */
public class TransactionManagerImpl implements TransactionManager{

    // XID文件头长度 - 存储事务计数器(xidCounter)的字节数
    static final int LEN_XID_HEADER_LENGTH = 8;
    // 每个事务状态占用的字节长度cc
    private static final int XID_FIELD_SIZE = 1;

    // 事务的三种状态定义
    private static final byte FIELD_TRAN_ACTIVE   = 0;// 事务活跃状态（正在进行中）
    private static final byte FIELD_TRAN_COMMITTED = 1;// 事务已提交状态
    private static final byte FIELD_TRAN_ABORTED  = 2;// 事务已中止状态

    // 超级事务，永远为commited状态，用于系统内部操作
    public static final long SUPER_XID = 0;
    // XID文件后缀为.xid
    static final String XID_SUFFIX = ".xid";
    // xidCounter为记录XID文件中的事务个数
    private long xidCounter;

    // 文件访问对象
    private RandomAccessFile file;
    // 文件通道，用于高效的文件I/O操作
    private FileChannel fc;

    // 锁，用于保护事务计数器的并发访问
    private Lock counterLock;

     /**
     * 构造函数
     * @param raf RandomAccessFile对象
     * @param fc FileChannel对象
     */
    TransactionManagerImpl(RandomAccessFile raf, FileChannel fc) {
        this.file = raf;
        this.fc = fc;
        // 创建可重入的互斥锁，用于保护事务计数器的并发访问
        counterLock = new ReentrantLock();
        // 检查并初始化XID计数器
        checkXIDCounter();
    }

    /**
     * 检查XID文件是否合法
     * 读取XID文件头中的xidcounter，根据它计算文件的理论长度，对比实际长度
     * 用于验证XID文件的完整性和一致性
     */
    private void checkXIDCounter() {
        long fileLen = 0;
        try {
            fileLen = file.length();
        } catch (IOException e1) {
            Panic.panic(Error.BadXIDFileException);
        }
        // 文件长度至少需要包含文件头
        if(fileLen < LEN_XID_HEADER_LENGTH) {
            Panic.panic(Error.BadXIDFileException);
        }

        // 从文件通道中读取8个字节的数据到ByteBuffer中（读取文件头，即事务个数）
        ByteBuffer buf = ByteBuffer.allocate(LEN_XID_HEADER_LENGTH);
        try {
            fc.position(0);  // 定位到文件开头
            fc.read(buf);  // 读取文件头内容
        } catch (IOException e) {
            Panic.panic(e);
        }
//      // 解析文件头中的事务计数器值
        this.xidCounter = Parser.parseLong(buf.array());
        // 计算根据xidCounter应有的文件长度
        long end = getXidPosition(this.xidCounter + 1);
        // 验证文件实际长度与理论长度是否一致
        if(end != fileLen) {
            Panic.panic(Error.BadXIDFileException);
        }
    }

    /**
     * 根据事务xid取得其在xid文件中对应的位置
     */

    private long getXidPosition(long xid) {
        return LEN_XID_HEADER_LENGTH + (xid-1)*XID_FIELD_SIZE;
    }

    /**
     * 更新指定事务的状态
     * @param xid 事务ID
     * @param status 新状态
     */
    private void updateXID(long xid, byte status) {
        // 计算事务状态在文件中的位置
        long offset = getXidPosition(xid);
        // 准备要写入的数据
        byte[] tmp = new byte[XID_FIELD_SIZE];
        tmp[0] = status;
        ByteBuffer buf = ByteBuffer.wrap(tmp);
        // 将buffer数据写入到filechannel中（将新状态写入文件）
        try {
            fc.position(offset);  // 定位到指定位置
            fc.write(buf);  // 写入状态数据
        } catch (IOException e) {
            Panic.panic(e);
        }
        //强制将所有对FileChannel的修改刷到磁盘上
        try {
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    /**
     * 增加XID计数器并更新文件头
     * 用于分配新的事务ID
     */
    private void incrXIDCounter() {
        xidCounter++;  // 增加计数器
        // 将新计数器值转换为字节数组
        ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter));
        try {
            fc.position(0);  // 定位到文件开头
            fc.write(buf);  // 写入新的计数器值
        } catch (IOException e) {
            Panic.panic(e);
        }
        // 强制将数据刷到磁盘
        try {
            fc.force(false);
        } catch (IOException e) {
            Panic.panic(e);
        }
    }

    /**
     * 开始一个新事务
     * @return 新事务的ID
     */
    public long begin() {
        // 加锁确保事务ID分配的唯一性
        counterLock.lock();
        try {
            // 分配新的事务ID（即当前计数器值+1）
            long xid = xidCounter + 1;
            // 在文件中标记该事务为活跃状态
            updateXID(xid, FIELD_TRAN_ACTIVE);
            // 事务计数器+1
            incrXIDCounter();
            return xid;
        } finally {
            // 释放锁
            counterLock.unlock();
        }
    }

    /**
     * 提交指定事务
     * @param xid 要提交的事务ID
     */
    public void commit(long xid) {
        // 在文件中标记该事务为已提交状态
        updateXID(xid, FIELD_TRAN_COMMITTED);
    }

    /**
     * 回滚指定事务
     * @param xid 要回滚的事务ID
     */
    public void abort(long xid) {
        // 在文件中标记该事务为已中止状态
        updateXID(xid, FIELD_TRAN_ABORTED);
    }

    /**
     * 检查指定事务是否处于特定状态
     * @param xid 事务ID
     * @param status 要检查的状态
     * @return 如果事务处于指定状态返回true，否则返回false
     */
    private boolean checkXID(long xid, byte status) {
        // 计算事务状态在文件中的位置
        long offset = getXidPosition(xid);
        // 创建缓冲区用于读取状态
        ByteBuffer buf = ByteBuffer.wrap(new byte[XID_FIELD_SIZE]);
        try {
            fc.position(offset);  // 定位到指定位置
            fc.read(buf);  // 读取状态数据
        } catch (IOException e) {
            Panic.panic(e);
        }
        // 比较读取的状态与期望状态
        return buf.array()[0] == status;
    }

    /**
     * 检查事务是否处于活跃状态
     * @param xid 事务ID
     * @return 如果事务活跃返回true，否则返回false
     */
    public boolean isActive(long xid) {
        // 与超级事务比较（超级事务不是活跃状态）
        if(xid == SUPER_XID) return false;
        return checkXID(xid, FIELD_TRAN_ACTIVE);
    }

    /**
     * 检查事务是否已提交
     * @param xid 事务ID
     * @return 如果事务已提交返回true，否则返回false
     */
    public boolean isCommitted(long xid) {
        // 超级事务总是已提交
        if(xid == SUPER_XID) return true;
        return checkXID(xid, FIELD_TRAN_COMMITTED);
    }

    /**
     * 检查事务是否已中止
     * @param xid 事务ID
     * @return 如果事务已中止返回true，否则返回false
     */
    public boolean isAborted(long xid) {
        // 超级事务不是中止状态
        if(xid == SUPER_XID) return false;
        return checkXID(xid, FIELD_TRAN_ABORTED);
    }

    /**
     * 关闭事务管理器，释放资源
     */
    public void close() {
        try {
            // 先关闭文件通道
            fc.close();
            // 再关闭文件
            file.close();
        } catch (IOException e) {
            Panic.panic(e);
        }
    }
}
